package eval.engine.cplex;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import com.google.inject.Inject;

import eval.deploy.IDemandManager;
import eval.deploy.IDeployManager;
import eval.engine.IPlacement;
import eval.engine.IPlacementEngine;
import eval.host.Host;
import eval.host.IHostManager;
import eval.host.compare.ISortHostList;
import eval.hostEvaluate.IHostEvaluate;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.vm.IVMManager;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;
import eval.engine.cplex.InitialCplexCode.CplexAdapter;
import eval.engine.cplex.InitialCplexCode.CplexDataHolder;
import eval.exceptions.CplexMapIndexToVmIdException;



public class cplexEngine implements IPlacementEngine {
	//Constant
	static final String CPLEX_PROPERTY_PREFIX      = "";
	static final String EQUALS = " = ";
	static final String EOL = " ;\n";
	static final String REWARD_WEIGHT_NAME         = "REWARD_WEIGHT";
	static final String PLACEMENT_WEIGHT_NAME      = "PLACEMENT_WEIGHT";
	static final String RED_LINE_WEIGHT_NAME       = "RED_LINE_WEIGHT";
	static final String ENERGY_WEIGHT_NAME         = "ENERGY_WEIGHT";
	static final String CAPACITY_OVERFLOW_WEIGHT_NAME = "CAPACITY_OVERFLOW_WEIGHT";
	static final String RELOCATION_WEIGHT_NAME     = "RELOCATION_WEIGHT";
	static final String USE_CPLEX_JNI = "useCplexJNI";
	// number of pieces for the piecewise approximation in the normal range.
	final static int PIECES_IN_NORMAL_RANGE = 12;
	// this is the number of pieces for the piecewise approximation in the normal range
	final static int PIECES_IN_UPPER_RANGE  = 20;
	// the number of segments for the piecewise linear function
	protected int nbPieces = PIECES_IN_NORMAL_RANGE + PIECES_IN_UPPER_RANGE;

	private double totalCpuCapacity = 0.0d;
	// default value for load relocated between two hosts to reduce the gap between them (when a VM is relocated between them)
	//TODO- check duplication
	float relocatedLoad;
	float relocationCost = 1.0f;
	// upper bound for normal range. No relocation (of average VM or bigger, 
	// that account for relocated.load load or smaller) in the range [0, upper.bound.normal.range[
	float upperBoundNormalRange;
	// minimal utilization gap between a 100% host and another host  to trigger relocation 
	// (of average VM or smaller, that account for relocated.load or bigger load)
	float utilizationMinimalGap;
	// The square coefficient for the normal range modelization of the load cost
	Double normalRangeSquareCoef = null; 
	// the X**3 coefficient for the upper range modelization of the load cost:
	Double highRangeLevel3Coef = null;


	private static final boolean DebugMode = true;

	//Variables
	private ExperimentConfiguration conf = ExperimentConfiguration.getSingleton();
	protected Map<Host, List<VirtualMachine>> vmMapping ;
	private long engineStartTime = 0;
	private long engineStopTime = 0;

	private IDeployManager deployManager;
	private IHostManager hostManager;
	private IVMManager vmManager;
	private IDemandManager demandManager;

	private List<Host> hosts;
	private int[] hostCapacityArray;
	private double[] hostLoadArray;
	private int[] HistoryDemandArray;

	private int tick;
	private int numOfHosts = 0;
	private int curNumOfVms = 0;
	private int prevNumOfVms = 0;

	private CplexMapIndexToVmId lastTickCplexMapIndexToVmId;
	private CplexMapIndexToVmId curCplexMapIndexToVmId;
	private CplexDataHolder preCplexData;
	private CplexDataHolder postCplexData;
	private CplexDataHolder lastTickCplexData;
	private int vmsToDeploy;


	@Inject
	public cplexEngine(IVMManager vmManager, IDeployManager deployManager,
			IHostManager hostManager, IDemandManager demandManager,
			ISortHostList sortHostList,IHostEvaluate hostEvaluator) {
		this.deployManager = deployManager;
		this.hostManager = hostManager;
		this.vmManager = vmManager;
		this.demandManager = demandManager;
		this.hosts = hostManager.getHosts();
		this.numOfHosts = conf.totalNumberOfHosts;
		this.hostCapacityArray = getHostCapacityArray();
		this.totalCpuCapacity = hostManager.getTotalCpuCapacity();
		this.HistoryDemandArray = new int[conf.experimentTicks];
	}

	@Override
	public IPlacement computePlacement(int tick, IPlacement lastPlacement) {
		/* 
		 * 	Initialize placement data   
		 */
		//update start time
		engineStartTime = System.currentTimeMillis();
		//current tick experiment 
		this.tick = tick;
		String cplexInput = "";
		
		//CPU demand of vm's already on cloud
		Map<VirtualMachine, Integer> cpuDemandOfVMs = demandManager.getCPUDemandOfAllVMsAtTick(tick);
		//MEM demand of vm's already on cloud
		Map<VirtualMachine, Integer> memDemandOfVMs = demandManager.getMEMDemandOfAllVMsAtTick(tick);
		boolean isOngoingOptimizationTick = (tick % conf.ongoingOptimizationPeriod) == 0;
		//list of vm's that going to be deploy  
		List<VirtualMachine> placedVmsAtCurTick =  new ArrayList<VirtualMachine>();
		//list of vm's that have been relocated
		List<VirtualMachine> relocatedVmsAtCurTick =  new ArrayList<VirtualMachine>();
		//list of vm's in Cloud at previous tick  
		List<VirtualMachine> vmInCloudAtPreTick =  hostManager.getAllVmAtOpenHosts();
		//list of vm's that ask to deploy but rejected.
		List<VirtualMachine> rejectedVmsAtCurTick =  new ArrayList<VirtualMachine>();
		
		//remove vm's list
		//List<VirtualMachine> vmsToRemoveAtCurTick = deployManager.getVMToBeRemovedAtTick(tick ,vmInCloudAtPreTick);
		List<VirtualMachine> vmsToRemoveAtCurTick = new ArrayList<VirtualMachine>();
		if(tick != 0)
//			 vmsToRemoveAtCurTick = this.deployManager.getVMToBeRemovedAtTick(tick ,vmMapping);
			vmsToRemoveAtCurTick = this.deployManager.getVMToBeRemovedAtTick(tick ,hostManager.getAllVmAtOpenHosts());
		
		//deploy new vm's list
		List<VirtualMachine> vmsToDeployAtCurTick = deployManager.getDeploySetAtTick(tick).getVMs();
		vmsToDeploy = vmsToDeployAtCurTick.size();
		//number of vm's
		prevNumOfVms = lastPlacement.getNumberOfVMsAtCloud();
		this.curNumOfVms = prevNumOfVms - vmsToRemoveAtCurTick.size() + vmsToDeployAtCurTick.size();
		//update placement data
		if((lastTickCplexData = lastPlacement.getCplexData()) == null) 
			lastTickCplexData =  new CplexDataHolder();
		if((lastTickCplexMapIndexToVmId = lastPlacement.getCplexMapIndexToVmId())== null) 
			lastTickCplexMapIndexToVmId = new CplexMapIndexToVmId();

		/* 
		 * Pre Placement calculation 
		 */

		//remove vm's from host
		removeVirtualMachines(vmsToRemoveAtCurTick);
		//update map from VMIdentifier to cplex id
		iniCurCplexMapIndexToVmId(vmsToRemoveAtCurTick,vmsToDeployAtCurTick,lastPlacement.getRejectedVMS());
		//update vm X host matrix for cplex  after we remove vmsToRemove 
		updateCplexData();

		this.HistoryDemandArray[tick] = getTotalCpuDemand();
		this.hostLoadArray = getHostLoadArray();

		//Prepare cplex string data
		if(DebugMode)
			cplexInput += addDebugDataToString(cplexInput);
		cplexInput += CreateInputStringForCplex(isOngoingOptimizationTick);
		if(DebugMode)
			System.out.println(cplexInput);
		printToFileSystem(cplexInput);
		//Prepare cplex engine
		CplexAdapter cplexAdapter = new CplexAdapter();


		/* 
		 * Compute placement by cplex 
		 */
		postCplexData =  cplexAdapter.computePlacement(cplexInput, curNumOfVms, numOfHosts);


		/* 
		 * Post placement calculation  	
		 */

		//Update map for placement
		updatePlacementMap(placedVmsAtCurTick,vmsToDeployAtCurTick,
				rejectedVmsAtCurTick,vmInCloudAtPreTick,relocatedVmsAtCurTick,cpuDemandOfVMs,memDemandOfVMs);
		try {//TODO chk logic
			updateLoadOnMap(cpuDemandOfVMs,memDemandOfVMs);
		} catch (Exception e) {
			e.printStackTrace();
		}
		CplexPlacement newPlacement = new CplexPlacement(vmMapping, hostManager, lastPlacement,
				demandManager, vmsToRemoveAtCurTick, vmsToDeployAtCurTick, placedVmsAtCurTick,
				rejectedVmsAtCurTick,relocatedVmsAtCurTick, tick,postCplexData,curCplexMapIndexToVmId);
		return  newPlacement;

	}


	/**
	 *  Map part
	 * @param rejected 
	 * 
	 * */

	private void iniCurCplexMapIndexToVmId(List<VirtualMachine> vmsToRemove,
			List<VirtualMachine> vmsToDeploy, List<VirtualMachine> rejected ){
		
		curCplexMapIndexToVmId = new CplexMapIndexToVmId(lastTickCplexMapIndexToVmId);
		if(rejected!=null &&rejected.size()>0)
			curCplexMapIndexToVmId.removeVmAtList(rejected);
		curCplexMapIndexToVmId.removeVmAtList(vmsToRemove);
		curCplexMapIndexToVmId.addVmAtList(vmsToDeploy);

	}

	private void updateCplexData(){
		preCplexData = new CplexDataHolder(lastTickCplexData,curCplexMapIndexToVmId,
				lastTickCplexMapIndexToVmId);
	}

	private int getHostIndexAtlastPlacmentMap(int vmIndex){
		int[] it = lastTickCplexData.getPlacementMapOfVm(vmIndex);
		for(int i=0;i<it.length;i++){
			if(it[i]==1)
				return i;
		}
		return -1;
	}
	
	private int getHostIndexAtPostPlacmentMap(int vmIndex){
		int[] it = postCplexData.getPlacementMapOfVm(vmIndex);
		for(int i=0;i<it.length;i++){
			if(it[i]==1)
				return i;
		}
		return -1;
	}

	private void removeVirtualMachines(List<VirtualMachine> vmsToRemove) {
		if(!vmsToRemove.isEmpty()){
			hostManager.removeListOfVmFromHosts(vmsToRemove);
		}
	}

	private void updatePlacementMap(List<VirtualMachine> placedVms,List<VirtualMachine> vmsToDeploy, 
			List<VirtualMachine> rejectedVms, List<VirtualMachine> vmsInCloud,List<VirtualMachine> relocatedVms,
			Map<VirtualMachine, Integer> cpuDemandOfVMs,Map<VirtualMachine, Integer> memDemandOfVMs) {
		//Initialize map
		vmMapping = new HashMap<Host, List<VirtualMachine>>(conf.totalNumberOfHosts);
		for(Host host : hosts)
			vmMapping.put(host, new ArrayList<VirtualMachine>());
		//open hosts by cplex active host post map
		for(Host host : vmMapping.keySet() ){
			if(postCplexData.getValAtActiveHostMap(host.getHostId())== 1 && host.isClose()) {
				hostManager.openHost(host,tick);
			}
		}
		VirtualMachine vm = null;
		Host newHost,oldHost;
		for(int vmIndexCur = 0 ; vmIndexCur < curNumOfVms; vmIndexCur++){
			VMIdentifier vmId = null;
			int vmCplexIndexLastTick,curHostIndex,lastHostIndex;
			try {
				vmId = curCplexMapIndexToVmId.getVmIdFromCplexIndex(vmIndexCur);
				vm = vmManager.getVMbyID(vmId);
			} catch (CplexMapIndexToVmIdException e) {
				System.out.println(e.getMessage());
				e.printStackTrace();
				System.exit(1);
			}
			
			try {
				vmCplexIndexLastTick = lastTickCplexMapIndexToVmId.getCplexIndexFromVmId(vmId);
				//check relocation
				lastHostIndex = getHostIndexAtlastPlacmentMap(vmCplexIndexLastTick);
				curHostIndex = getHostIndexAtPostPlacmentMap(vmIndexCur);
				if(curHostIndex!=-1) {
					if(lastHostIndex!=curHostIndex) {//relocate
						newHost = hostManager.getHostByID(curHostIndex);
						oldHost = hostManager.getHostByID(lastHostIndex);
						int sumMinDemand = 0;
						for(VirtualMachine vmAtNewHost : hostManager.getVmsAtHost(newHost)) {
							int minDEmand = vmAtNewHost.getVMConfiguration().getMinCpu();
							int mem = memDemandOfVMs.get(vmAtNewHost);
							vmManager.setSatisfiedLoad(vmAtNewHost, minDEmand, mem);
							sumMinDemand += minDEmand;
						}
						try {
							hostManager.setHostCurrentCpuLoad(newHost, sumMinDemand);
						} catch (Exception e1) {
							
						} 
						hostManager.relocateVm(vm,oldHost,newHost,
								vm.getVMConfiguration().getMinCpu(),memDemandOfVMs.get(vm));
						relocatedVms.add(vm);
						vmMapping.get(newHost).add(vm);
					} else {
						newHost = hostManager.getHostByID(curHostIndex);
						vmMapping.get(newHost).add(vm);
					}
				} else {//rejected
					rejectedVms.add(vm);
					hostManager.rejectVms(vm);
				}
				
			} catch (CplexMapIndexToVmIdException e) {//vm is new deploy
				curHostIndex = getHostIndexAtPostPlacmentMap(vmIndexCur);
				if(curHostIndex!=-1) {
					
					newHost = hostManager.getHostByID(curHostIndex);
					int vmCpuLoad = Math.min(cpuDemandOfVMs.get(vm).intValue(),vm.getVMConfiguration().getMinCpu());
					int vmMemLOad = memDemandOfVMs.get(vm).intValue();
					hostManager.addVmToHost(newHost,vm,vmCpuLoad, vmMemLOad);
					placedVms.add(vm);
					deployManager.setVmStartLife(vm, tick);
					vmMapping.get(newHost).add(vm);
					vmManager.setSatisfiedLoad(vm, vmCpuLoad, vmMemLOad);
				} else { //rejected
					rejectedVms.add(vm);
					hostManager.rejectVms(vm);
				}
				
			}
		}
		for(Host host : vmMapping.keySet() ){
			if(postCplexData.getValAtActiveHostMap(host.getHostId()) == 0 && host.isOpen()) {
				hostManager.closeHost(host);
			}
		}
	}
	
	private void updateVmMap() {
		for(Host host : hosts) {
			if(host.isOpen()) {
				vmMapping.put(host, hostManager.getVmsAtHost(host));
			}
		}
	}
	
	private void updateHostLoad(Host hostToUpdate,
			List<VirtualMachine> vmListToUpdate,
			Map<VirtualMachine, Integer> cpuDemandOfVMs, 
			Map<VirtualMachine, Integer> memDemandOfVMs) {
		int sumMinDemand = 0;
		for(VirtualMachine vm : vmListToUpdate) {
			int minDEmand = vm.getVMConfiguration().getMinCpu();
			vm.setSatisfiedCpuDemand(minDEmand);
			sumMinDemand += minDEmand;
		}
		try {
			hostManager.setHostCurrentCpuLoad(hostToUpdate, sumMinDemand);
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			System.exit(1);
		}
		int vmNotSatisfied,tmpLoad;
		vmNotSatisfied = 0;
		//count UnSatisfied CPU demand of vm's
		for(VirtualMachine vm : vmListToUpdate) {
			tmpLoad = vm.getSatisfiedCpuDemand();
			if(cpuDemandOfVMs.get(vm).intValue()>tmpLoad)
				vmNotSatisfied++;
		}
		//give the rest of cpu demand 
		int loadToShare = hostManager.getCpuAvailableAtHost(hostToUpdate);
		int cutOfThePie;
		while (vmNotSatisfied > 0 && loadToShare >0) {
			cutOfThePie = loadToShare/vmNotSatisfied;
			if(cutOfThePie!=0) { 
				for(VirtualMachine vm : vmListToUpdate) {
					//int a = cpuDemandOfVMs.get(vm).intValue();
					tmpLoad = cpuDemandOfVMs.get(vm).intValue()- vm.getSatisfiedCpuDemand();
					if(tmpLoad>0) {
						tmpLoad = Math.min(cutOfThePie,tmpLoad);
						vm.updateSatisfiedCpuDemand(tmpLoad);
						try {
							hostManager.updateHostCurrentCpuLoad(hostToUpdate,tmpLoad);
						} catch (Exception e) {
							e.printStackTrace();
						}
						loadToShare -= tmpLoad;
						if((cpuDemandOfVMs.get(vm).intValue()- vm.getSatisfiedCpuDemand())<=0)
							vmNotSatisfied--;
					}
				}
			} else {
				loadToShare =0;
			}
		}
	}
	
	private void updateLoadOnMap(Map<VirtualMachine, Integer> cpuDemandOfVMs ,Map<VirtualMachine, Integer> memDemandOfVMs) throws Exception {
		for(Host host : vmMapping.keySet() ) {
			updateHostLoad(host,vmMapping.get(host),cpuDemandOfVMs,memDemandOfVMs);
		}
	}
	

	@Override
	public double getEngineTimeInSec() {
		return engineStopTime - engineStartTime;
	}

	@Override
	public void resetEngineTime() {
		engineStartTime = engineStopTime = 0;
	}


	/**
	 * Cplex part 
	 * 
	 * */

	private String addDebugDataToString(String cplexInput) {
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		cplexInput += "\n\n////////////////new cycle////////////\n";
		cplexInput += "/////////////////////////////////////\n";
		cplexInput += "//tick = " + tick +"\n\n" ;
		cplexInput += "//Mapping of index in placement table to vmID:\n" ;
		for(int i=0; i<it.length;i++)
			cplexInput += "//" + i + " -> " + it[i] + "\n";
		cplexInput += "//Mapping of index in placement table to HostID:\n" ;
		for(int i=0; i<hosts.size();i++)
			cplexInput += "//" + i + " -> " + hosts.get(i).getHostId() + "\n";
		cplexInput += "\n\n";
		return cplexInput;
	}

	private String CreateInputStringForCplex(boolean isOngoingOptimizationTick){
		String inputStringForCplex = 

				"REWARD_WEIGHT = " + (int)(conf.profitPercent * 1000) + ";\n" +
						"PLACEMENT_WEIGHT = " + conf.PLACEMENT_WEIGHT  + ";\n" +
						"RED_LINE_WEIGHT = " + conf.RED_LINE_WEIGHT +";\n" +
						"ENERGY_WEIGHT = " + (int)(conf.powerPercent * 1000) + ";\n" +
						"nbHosts = " + numOfHosts + ";\n" +
						"nbVM = " + curNumOfVms + ";\n" + 
						"nbResources = " + conf.nbResources + ";\n" +
						"capacity = " + getHostsCapacityArray() +";\n" +
						"reservation = " + getVMreservationArray() + ";\n" +
						"vsCpuDemand = " + getVMCpuDemandArray(tick) + ";\n" +
						"previousPlacement = " + Arrays.deepToString(preCplexData.getPlacementMap()) + ";\n" +
						"fixed = " + getFixedArray(isOngoingOptimizationTick) + ";\n" +
						"gradient = " + calcCloudGraphGradient() + ";\n" + 
						"numberOfRelocation =" + getNumberOfRelocation(isOngoingOptimizationTick) + EOL +
						"redLine =" + Math.round(conf.HostRedLine) + ";\n" +
						"hostBaseEnergyConsumption = " + getBaseEnergyConsArray() + ";\n" +
						"hostPowerOnEnergyConsumption = " + getPowerONEngConsArray() + ";\n" +
						"activeHosts = " + Arrays.toString(preCplexData.getActiveHostsMap()) /*getActiveHosts()*/ + ";\n" +
						RELOCATION_WEIGHT_NAME + EQUALS + conf.RELOCATION_WEIGHT + EOL +
						CAPACITY_OVERFLOW_WEIGHT_NAME + EQUALS + conf.CAPACITY_OVERFLOW_WEIGHT + EOL +
						"relocationCost = " +Arrays.toString(getRelocationCost()) + EOL +
						"loadBalancingActivated = " + conf.loadBalancingActivated + EOL +
						"nbPieces = " + nbPieces + EOL +
						"loadCostSlopes = [0.9090906923468016, 2.727272077040405,4.5454534617340085, 6.363634846427611, 8.181816231121212,9.999997615814822, 11.818179000508422, 13.636360385202021,15.45454176989563, 17.272723154589222, 19.09090453928284,20.90908592397645, 100.52474004395609, 106.47300396732521,112.58968698792097, 118.87478910574212, 125.32831032078963,131.9502506330635, 138.74061004256336, 145.69938854929075,152.82658615324192, 160.12220285442078, 167.58623865282559,175.21869354845592, 183.0195675413143, 190.98886063139614,199.1265728187067, 207.4327041032418, 215.9072544850035,224.55022396399326, 233.3616125402043, 242.3414202136466] " +EOL+ 
						"loadBreakpoints = [0.04999999701976776, 0.09999999403953552,0.14999999105930328, 0.19999998807907104, 0.2499999850988388,0.29999998211860657, 0.34999997913837433, 0.3999999761581421,0.44999997317790985, 0.4999999701976776, 0.5499999672174454,0.5999999642372131, 0.6199999660253525, 0.6399999678134918,0.6599999696016312, 0.6799999713897705, 0.6999999731779099,0.7199999749660492, 0.7399999767541885, 0.7599999785423279,0.7799999803304672, 0.7999999821186066, 0.8199999839067459,0.8399999856948852, 0.8599999874830246, 0.8799999892711639,0.8999999910593033, 0.9199999928474426, 0.9399999946355819,0.9599999964237214, 0.9799999982118606] " + EOL+
						"currentLoadBalancingScore = 30.0" + EOL+//+ computeCurrentLoadBalancingScore(getVMCpuDemandArray(tick), hostCapacityArray)   + EOL+
						//"currentLoadBalancingScore = " + computeCurrentLoadBalancingScore(null, hostCapacityArray)   + EOL +
						"idealLoadBalancingScore = 20.0" + EOL //+ computeBestLoadBalancingScore(HistoryDemandArray[tick] /*totalCpuDemand*/) + EOL;
						//"idealLoadBalancingScore = " + computeBestLoadBalancingScore(HistoryDemandArray[tick] /*totalCpuDemand*/) + EOL;
						+"penaltyOpenHostOnSpot = " + conf.penaltyOpenHostOnSpot + EOL;
		return inputStringForCplex;
	}

	private int getNumberOfRelocation(boolean isOngoingOptimizationTick) {
		int toRet =  vmsToDeploy;
		if(isOngoingOptimizationTick) {
			toRet += conf.numberOfRelocation;
		}
		return toRet;
	}

	private String getHostsCapacityArray(){
		String HostCapacity = "[\n";
		//TODO: sort hosts.
		for(Host host : hostManager.getHosts()){
			HostCapacity += "[" + host.getHostCapacity().getCpuCapacity() + " " + host.getHostCapacity().getCpuCapacity() + " " + host.getHostCapacity().getMemCapacity() + "]\n" ;
		}
		HostCapacity += "]";
		return HostCapacity;
	}

	private String getVMreservationArray(){
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		String vmReservation = "\n[";
		//TODO: sort vm's
		for(VMIdentifier vmID : it){
			VirtualMachine vm = vmManager.getVMbyID(vmID);
			vmReservation += "[0 " + vm.getVMConfiguration().getMinCpu() + " " + demandManager.getMemDemandOfVmAtTick(vm, tick) + "]\n";	
		}
		vmReservation += "]";
		return vmReservation;
	}

	private String getVMCpuDemandArray(int tick){
		VMIdentifier[] it = curCplexMapIndexToVmId.getCplexIndexToVmId();
		String vmsCpuDemand = "\n[ ";
		//TODO: sort vms
		for(VMIdentifier vmID : it){
			vmsCpuDemand += demandManager.getCpuDemandOfVmAtTick(vmID,tick) + ", ";
		}
		vmsCpuDemand = vmsCpuDemand.substring(0, vmsCpuDemand.length() - 2);
		vmsCpuDemand += "]";
		return vmsCpuDemand;
	}


	private String getFixedArray(boolean isOngoingOptimizationTick) {
		String fixedArray = "\n[";
		for(int i=0; i< curNumOfVms; i++) {
			if(isOngoingOptimizationTick){
				fixedArray += "0, ";
			} else {
				try {
					lastTickCplexMapIndexToVmId.getCplexIndexFromVmId(
							curCplexMapIndexToVmId.getVmIdFromCplexIndex(i));
					fixedArray += "1 ,";
				} catch (CplexMapIndexToVmIdException e) {
					fixedArray += "0, ";
				}
			}
		}
		fixedArray = fixedArray.substring(0, fixedArray.length() - 2);
		fixedArray += "]";
		return fixedArray;
	}

	private String getBaseEnergyConsArray() {
		String baseEnergyCons = "\n[";
		//TODO: sort hosts
		for(Host host :hostManager.getHosts()){	
			baseEnergyCons += host.getHostCapacity().getHostBaseEnergyConsumption() + ", ";
		}
		baseEnergyCons = baseEnergyCons.substring(0, baseEnergyCons.length() - 2);
		baseEnergyCons += "]";
		return baseEnergyCons;
	}

	private String getPowerONEngConsArray() {
		String PowerOnEnergyCons = "\n[";
		//TODO: sort hosts
		for(Host host :hostManager.getHosts()){	
			PowerOnEnergyCons += host.getHostCapacity().getHostPowerOnEnergyConsumption() + ", ";
		}
		PowerOnEnergyCons = PowerOnEnergyCons.substring(0, PowerOnEnergyCons.length() - 2);
		PowerOnEnergyCons += "]";
		return PowerOnEnergyCons;
	}

	private int getTotalCpuDemand(){
		if(tick==0)
			return 0;
		int totalDemand = 0;
		for(Host host :vmMapping.keySet()) {
			for(VirtualMachine vm :vmMapping.get(host)) {
				totalDemand += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			}
		}
		return totalDemand;
	}

	private float[] getRelocationCost(){
		float[] RelocationCosts = new float[curNumOfVms];
		for(int i=0; i<curNumOfVms ; i++){
			RelocationCosts[i] = relocationCost ;
		}
		return RelocationCosts;

	}

protected int calcCloudGraphGradient() {
		
		int historyWindowLength = conf.historyWindowLength;
		
		if(tick == 0 || tick == 1){return 0;}
		
		int length = Math.min(historyWindowLength, tick);
		
		int[] historyArray;	
		int[] historyWindowArray = new int[length];
		int[] GradientsArray; 
		int[] smoothedGradientsArray; 
		for(int i=0 ; i<length; i++ )
			historyWindowArray[i] = HistoryDemandArray[tick - length + i];
		
		historyArray = runHoltWinterAlg(historyWindowArray);
		GradientsArray = getGradientsArray(historyArray);
		smoothedGradientsArray = runHoltWinterAlg(GradientsArray);
		
		return smoothedGradientsArray[smoothedGradientsArray.length -1];
	}

	private int[] runHoltWinterAlg(int[] ArrayTosmooth){
		int length = ArrayTosmooth.length;
		double alfa = conf.alfaInputForHWalgo;
		int[] smoothedArray = new int[length];
		//initial condition.
		smoothedArray[0] = ArrayTosmooth[0];

		//algorithm function: s(t) = alfa*x(t-1) + (1-alfa)*s(t-1)
		for(int i=1; i< length ;i++ ){
			smoothedArray[i] = (int) (alfa * ArrayTosmooth[i-1] + (1-alfa) * smoothedArray[i-1]);  
		}

		return smoothedArray;
	}
	

	
	private int[] getGradientsArray(int[] inputArray){
		int k = (int)(inputArray.length * conf.gradientwidth);
//		 int k = (int)(conf.historyWindowLength * conf.gradientwidth);
		int outputArray[] = new int[k];
		for(int i=0; i<k ;i++)
			outputArray[i] = (int)((inputArray[i+k] - inputArray[i]) / k);

		return outputArray;
	}



	private int[] getHostCapacityArray() {
		int[] hostCapacityArray = new int[conf.totalNumberOfHosts];
		for(Host host : hosts){
			hostCapacityArray[host.getHostId()] = host.getHostCapacity().getCpuCapacity();
		}

		return hostCapacityArray;
	}

	private double[] getHostLoadArray(){
		VMIdentifier[] it = lastTickCplexMapIndexToVmId.getCplexIndexToVmId();
		double[] currentHostCpuLoad = new double[numOfHosts];
		double sumOfLoudInHost = 0;
		for(int hostIndex =0 ; hostIndex<numOfHosts ;hostIndex++){
			for(int vmIndex=0; vmIndex < prevNumOfVms; vmIndex++){
				if(lastTickCplexData.getValAtPlacementMap(vmIndex,hostIndex) == 1){
					VMIdentifier vmID = it[vmIndex];
					sumOfLoudInHost += demandManager.getCpuDemandOfVmAtTick(vmID,tick);
				}

			}
			if(sumOfLoudInHost > hostCapacityArray[hostIndex]){
				currentHostCpuLoad[hostIndex] = hostCapacityArray[hostIndex];
			}
			else{
				currentHostCpuLoad[hostIndex] = sumOfLoudInHost;
			}

		}

		return currentHostCpuLoad;
	}

	private void printToFileSystem(String CplexInput){
		try{
			File file = new File(conf.CplexInputFilePath);

			if(tick == 0){
				file.delete();
				file = new File(conf.CplexInputFilePath);
			}

			if (!file.exists()) {
				file.createNewFile();
			}



			FileWriter fw = new FileWriter(file.getAbsoluteFile(),true);
			BufferedWriter bw = new BufferedWriter(fw);
			bw.write(CplexInput);
			bw.close();

		}catch (Exception e){//Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

	/**
	 * Never used  
	 * 
	 * */
	//TODO

	protected Double[] computeSlopes(Double[] loadBreakpoints) {

		Double[] loadCostSlopes = new Double[nbPieces]; 

		// Normal range:
		{
			double segmentLength = (upperBoundNormalRange/PIECES_IN_NORMAL_RANGE);

			loadCostSlopes[0] = (loadCostFunction(loadBreakpoints[0]) - loadCostFunction(0.0)) / segmentLength;

			for (int index=1; index<PIECES_IN_NORMAL_RANGE-1; index++) {
				loadCostSlopes[index]  = (loadCostFunction(loadBreakpoints[index]) - loadCostFunction(loadBreakpoints[index-1])) / segmentLength;
			}

			loadCostSlopes[PIECES_IN_NORMAL_RANGE-1]  = (loadCostFunction(upperBoundNormalRange) - loadCostFunction(loadBreakpoints[PIECES_IN_NORMAL_RANGE-2])) / segmentLength;

		}

		// Upper range:
		{
			double segmentLength = (1.0 - upperBoundNormalRange)/PIECES_IN_UPPER_RANGE;

			for (int index=0; index<PIECES_IN_UPPER_RANGE-1; index++) {
				loadCostSlopes[PIECES_IN_NORMAL_RANGE + index]  = 
						(
								loadCostFunction(loadBreakpoints[PIECES_IN_NORMAL_RANGE-1 +index+1]) - 
								loadCostFunction(loadBreakpoints[PIECES_IN_NORMAL_RANGE-1 +index])
								) / segmentLength;
			}

			loadCostSlopes[PIECES_IN_NORMAL_RANGE + PIECES_IN_UPPER_RANGE - 1]  = (loadCostFunction(1.0) - loadCostFunction(1.0 - segmentLength)) / segmentLength;
		}

		return loadCostSlopes;
	}

	protected Double[] computeBreakPoints() {
		Double[] loadBreakpoints = new Double[nbPieces-1]; 

		// Normal range:
		{
			double segmentLength = (upperBoundNormalRange/PIECES_IN_NORMAL_RANGE);

			for (int index=0; index<PIECES_IN_NORMAL_RANGE; index++) {
				loadBreakpoints[index] = segmentLength * (index+1);
			}
		}

		// Upper range:
		{
			double segmentLength = (1.0 - upperBoundNormalRange)/PIECES_IN_UPPER_RANGE;

			for (int index=0; index<PIECES_IN_UPPER_RANGE-1; index++) {
				loadBreakpoints[PIECES_IN_NORMAL_RANGE + index] = upperBoundNormalRange + segmentLength * (index+1);
			}
		}
		return loadBreakpoints;
	}


	double loadCostFunction(double d) {
		String METHOD = "loadCostFunction";


		if (d<0.0 || d>1.0) {
			throw new RuntimeException(METHOD + " invoked with an invalid argument " + d);
		}

		if (normalRangeSquareCoef == null || highRangeLevel3Coef == null) {
			throw new RuntimeException(METHOD + " invoked prior to the coefficient computations. Coef values: " + normalRangeSquareCoef + ", " + highRangeLevel3Coef);
		}

		// 0.0 < d <= upperBoundNormalRange case:
		if (d<=upperBoundNormalRange) {
			return normalRangeSquareCoef * Math.pow(d,2);
		}

		// upperBoundNormalRange < d < 1.0 case:
		return highRangeLevel3Coef * (Math.pow(d,3) - Math.pow(upperBoundNormalRange, 3)) +  normalRangeSquareCoef * Math.pow(d, 2); 
	}


	double loadCostFirstDerivative(double load) {
		String METHOD = "loadCostFirstDerivative";

		if (load<0.0 || load>1.0) {
			throw new RuntimeException(METHOD + " invoked with an invalid argument " + load);
		}

		if (normalRangeSquareCoef == null || highRangeLevel3Coef == null) {
			throw new RuntimeException(METHOD + " invoked prior to the coefficient computations. Coef values: " +
					normalRangeSquareCoef + ", " + highRangeLevel3Coef);
		}

		// 0.0 < d <= upperBoundNormalRange case:
		if (load<=upperBoundNormalRange) {
			return 2.0f * normalRangeSquareCoef * load;
		}

		// upperBoundNormalRange < d < 1.0 case:
		return 3.0f * highRangeLevel3Coef * Math.pow(load,2) +  2.0f * normalRangeSquareCoef * load; 
	}


	/**
	 * 	
	 * Amit second parameter was changed from int[][] hostCapacity to int[] hostCapacity
	 */

	private Double computeCurrentLoadBalancingScore(int[] vsDemands,/* int[][] hostCapacity*/ int[] hostCapacity) { 

		double currentLoadScore = 0.0;

		for (int hostIndex=0; hostIndex<numOfHosts; hostIndex++) {
			/**
			 * Amit:  changed  hostCapacity[hostIndex][RESOURCE_DEMAND_INDEX] to  hostCapacity[hostIndex]
			 */
			double currentLoad = Math.min(1.0, hostLoadArray[hostIndex] / hostCapacity[hostIndex]/*[RESOURCE_DEMAND_INDEX]*/);
			// currentHostCpuLoad[hostIndex] is based on expected demand and therefore may be bigger than hostCapacity[hostIndex][RESOURCE_DEMAND_INDEX]
			currentLoadScore += (currentLoad *4); //loadCostFunction(currentLoad);
		}

		return currentLoadScore;
	}


	private Double computeBestLoadBalancingScore(double totalDemand) {

		int loadBalancingActivated = 1;  // default is activated

		if (totalCpuCapacity < totalDemand) {
			// Case where the CPU demand can not be fully satisfied
			// the optimization should strive at satisfying as much
			// CPU demand as possible.  In this case there is little
			// need to load balance CPU.
			// We use as totalCpuDemand the current satisfied demand 
			// and disable the load balancing mechanism
			totalDemand = totalCpuCapacity;
			loadBalancingActivated = 0;
		}

		double bestCommonLoad = totalDemand / totalCpuCapacity;

		//			OplUtilities.addIntValue(this, LOADBALANCINGACTIVATED, loadBalancingActivated, sbFile, useCplexJNI);

		return numOfHosts * bestCommonLoad * 5;	//loadCostFunction(bestCommonLoad);
	}


	private String getActiveHosts() {
		String getActiveHosts ="\n[";
		for(Host host :hostManager.getHosts()){
			if(host.isOpen()){
				getActiveHosts += "1, ";
			}
			else{
				getActiveHosts += "0, ";
			}
		}
		getActiveHosts = getActiveHosts.substring(0, getActiveHosts.length() - 2);
		getActiveHosts += "]";
		return getActiveHosts;
	}






}